package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"google.golang.org/grpc"
	"gitlab.com/gitlab-org/gitaly/v14/client"
	"gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config"
	"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
)

type archiveSubcommand struct {
	configPath     string
	projectID      int64
	repositoryPath string
	storageName    string
	gitlabToken    string
	gitlabURL      string
	mergedAfter    string
	dryRun         bool
	creatorID      int64
	creatorName    string
	reason         string
	initMode       bool   // 初始化模式，第一次创建回收站仓库
}

// GitLabMR represents a merge request from GitLab API
type GitLabMR struct {
	IID         int    `json:"iid"`
	Title       string `json:"title"`
	State       string `json:"state"`
	MergedAt    string `json:"merged_at"`
	TargetBranch string `json:"target_branch"`
	SourceBranch string `json:"source_branch"`
}

func (cmd *archiveSubcommand) Flags(fs *flag.FlagSet) {
	fs.StringVar(&cmd.configPath, "config", "", "Gitaly配置文件路径 (必需)")
	fs.Int64Var(&cmd.projectID, "project-id", 0, "GitLab项目ID (必需)")
	fs.StringVar(&cmd.repositoryPath, "repository-path", "", "仓库路径 (必需)")
	fs.StringVar(&cmd.storageName, "storage-name", "", "Gitaly存储名称 (如果不指定则使用配置中的第一个存储)")
	fs.StringVar(&cmd.gitlabToken, "gitlab-token", "", "GitLab API访问令牌 (必需)")
	fs.StringVar(&cmd.gitlabURL, "gitlab-url", "https://gitlab.com", "GitLab实例URL")
	fs.StringVar(&cmd.mergedAfter, "merged-after", "", "获取指定日期后合并的MR (格式: 2006-01-02T15:04:05Z)")
	fs.BoolVar(&cmd.dryRun, "dry-run", false, "仅显示将要处理的引用，不执行实际操作")
	fs.Int64Var(&cmd.creatorID, "creator-id", 0, "操作创建者ID")
	fs.StringVar(&cmd.creatorName, "creator-name", "gitaly-tools", "操作创建者名称")
	fs.StringVar(&cmd.reason, "reason", "Auto archive merged MRs", "归档操作的原因说明")
	fs.BoolVar(&cmd.initMode, "init", false, "初始化模式：同时处理所有keep-around引用和已合并的MR引用")
}

func (cmd *archiveSubcommand) Run(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
	// 验证必需参数
	if cmd.configPath == "" {
		return fmt.Errorf("config路径不能为空")
	}
	if cmd.projectID == 0 {
		return fmt.Errorf("project-id不能为空")
	}
	if cmd.repositoryPath == "" {
		return fmt.Errorf("repository-path不能为空")
	}
	if cmd.gitlabToken == "" {
		return fmt.Errorf("gitlab-token不能为空")
	}

	// 加载Gitaly配置
	cfg, err := config.Load(cmd.configPath)
	if err != nil {
		return fmt.Errorf("加载配置文件失败: %v", err)
	}

		var allArchiveRefs []*gitalypb.ArchiveRef

	// 处理已合并的MR
	mergedMRs, err := cmd.fetchMergedMRs(ctx)
	if err != nil {
		return fmt.Errorf("获取已合并MR列表失败: %v", err)
	}

	if len(mergedMRs) > 0 {
		fmt.Fprintf(stdout, "找到 %d 个已合并的MR需要处理\n", len(mergedMRs))

		// 构建MR归档引用列表
		for _, mr := range mergedMRs {
			sourceRef := fmt.Sprintf("refs/merge-requests/%d/head", mr.IID)
			sourceCommitID, err := cmd.getRefCommitID(ctx, cfg, sourceRef)
			if err != nil {
				fmt.Fprintf(stdout, "跳过MR %d，无法获取commit ID: %v\n", mr.IID, err)
				continue
			}

			allArchiveRefs = append(allArchiveRefs, &gitalypb.ArchiveRef{
				SourceRefName:  sourceRef,
				SourceCommitId: sourceCommitID,
				TargetRefName:  fmt.Sprintf("archive/merge-requests/%d/head", mr.IID),
			})

			fmt.Fprintf(stdout, "将归档MR引用: %s -> archive/merge-requests/%d/head (MR: %s)\n",
				sourceRef, mr.IID, mr.Title)
		}
	}

	// 如果是初始化模式，还要处理所有keep-around引用
	if cmd.initMode {
		keepAroundRefs, err := cmd.fetchAllKeepAroundRefs(ctx, cfg)
		if err != nil {
			return fmt.Errorf("获取keep-around引用失败: %v", err)
		}

		if len(keepAroundRefs) > 0 {
			fmt.Fprintf(stdout, "初始化模式：找到 %d 个keep-around引用需要处理\n", len(keepAroundRefs))

			for refName, commitID := range keepAroundRefs {
				allArchiveRefs = append(allArchiveRefs, &gitalypb.ArchiveRef{
					SourceRefName:  refName,
					SourceCommitId: commitID,
					TargetRefName:  fmt.Sprintf("archive/%s", refName),
				})

				shortCommitID := commitID
				if len(commitID) > 8 {
					shortCommitID = commitID[:8]
				}
				fmt.Fprintf(stdout, "将归档keep-around引用: %s -> archive/%s (commit: %s)\n",
					refName, refName, shortCommitID)
			}
		}
	}

	// 此时所有引用都已经添加到allArchiveRefs中了

	if len(allArchiveRefs) == 0 {
		fmt.Fprintf(stdout, "未找到需要处理的引用\n")
		return nil
	}

	if cmd.dryRun {
		fmt.Fprintf(stdout, "dry-run模式: 以上 %d 个引用将被添加到归档等待列表\n", len(allArchiveRefs))
		return nil
	}

	// 连接到Gitaly服务并执行归档操作
	if err := cmd.performArchiveOperation(ctx, cfg, allArchiveRefs, stdout); err != nil {
		return fmt.Errorf("执行归档操作失败: %v", err)
	}

	return nil
}

func (cmd *archiveSubcommand) fetchMergedMRs(ctx context.Context) ([]GitLabMR, error) {
	baseURL := strings.TrimSuffix(cmd.gitlabURL, "/")
	apiURL := fmt.Sprintf("%s/api/v4/projects/%d/merge_requests", baseURL, cmd.projectID)

	var allMRs []GitLabMR
	page := 1
	perPage := 100

	// 创建HTTP客户端
	httpClient := &http.Client{
		Timeout: 30 * time.Second,
	}

	for {
		// 构建查询参数
		params := url.Values{}
		params.Set("state", "merged")
		params.Set("per_page", fmt.Sprintf("%d", perPage))
		params.Set("page", fmt.Sprintf("%d", page))
		params.Set("order_by", "updated_at")
		params.Set("sort", "desc")

		if cmd.mergedAfter != "" {
			params.Set("updated_after", cmd.mergedAfter)
		}

		fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode())

		// 创建HTTP请求
		req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
		if err != nil {
			return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
		}

		// 设置认证头
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cmd.gitlabToken))
		req.Header.Set("Content-Type", "application/json")

		// 发送请求
		resp, err := httpClient.Do(req)
		if err != nil {
			return nil, fmt.Errorf("发送HTTP请求失败: %v", err)
		}

		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()
			return nil, fmt.Errorf("GitLab API返回错误状态: %d", resp.StatusCode)
		}

		// 解析响应
		var pageMRs []GitLabMR
		if err := json.NewDecoder(resp.Body).Decode(&pageMRs); err != nil {
			resp.Body.Close()
			return nil, fmt.Errorf("解析GitLab API响应失败: %v", err)
		}
		resp.Body.Close()

		// 如果没有更多数据，退出循环
		if len(pageMRs) == 0 {
			break
		}

		// 过滤并添加到结果中
		for _, mr := range pageMRs {
			if mr.State == "merged" && mr.MergedAt != "" {
				allMRs = append(allMRs, mr)
			}
		}

		// 如果当前页的结果少于每页的数量，说明已经是最后一页
		if len(pageMRs) < perPage {
			break
		}

		page++
	}

	return allMRs, nil
}

func (cmd *archiveSubcommand) performArchiveOperation(ctx context.Context, cfg config.Cfg, archiveRefs []*gitalypb.ArchiveRef, stdout io.Writer) error {
	// 确定存储名称
	var storageName string
	if cmd.storageName != "" {
		storageName = cmd.storageName
	} else if len(cfg.Storages) > 0 {
		storageName = cfg.Storages[0].Name
	} else {
		return fmt.Errorf("配置中未找到任何存储，请使用-storage-name参数指定")
	}

	// 连接到Gitaly服务
	address := cfg.SocketPath
	if address == "" {
		// 如果没有配置socket路径，使用默认地址
		address = "unix:/tmp/gitaly.socket"
	}

	conn, err := client.DialContext(ctx, address, client.DefaultDialOpts)
	if err != nil {
		return fmt.Errorf("连接Gitaly服务失败 (%s): %v", address, err)
	}
	defer conn.Close()

	// 创建Archive服务客户端
	archiveClient := gitalypb.NewArchiveServiceClient(conn)

	// 构建归档请求
	request := &gitalypb.ManageRefsRequest{
		Repository: &gitalypb.Repository{
			StorageName:  storageName,
			RelativePath: cmd.repositoryPath,
		},
		Action:      gitalypb.ArchiveOperation_ARCHIVE,
		Refs:        archiveRefs,
		CreatorId:   cmd.creatorID,
		CreatorName: cmd.creatorName,
		Reason:      cmd.reason,
	}

	// 执行归档操作
	response, err := archiveClient.ManageRefs(ctx, request)
	if err != nil {
		return fmt.Errorf("调用ManageRefs失败: %v", err)
	}

	// 输出操作结果
	fmt.Fprintf(stdout, "归档操作完成:\n")
	fmt.Fprintf(stdout, "  任务ID: %s\n", response.TaskId)
	fmt.Fprintf(stdout, "  已处理: %d 个引用\n", response.ProcessedCount)

	if len(response.FailedRefs) > 0 {
		fmt.Fprintf(stdout, "  失败的引用: %v\n", response.FailedRefs)
	}

	fmt.Fprintf(stdout, "\n提示: 引用已写入元数据并发送到Kafka队列等待处理\n")
	fmt.Fprintf(stdout, "可以使用任务ID '%s' 跟踪处理状态\n", response.TaskId)

	return nil
}



// getRefCommitID gets the commit ID for a specific reference
func (cmd *archiveSubcommand) getRefCommitID(ctx context.Context, cfg config.Cfg, refName string) (string, error) {
	// 确定存储名称
	var storageName string
	if cmd.storageName != "" {
		storageName = cmd.storageName
	} else if len(cfg.Storages) > 0 {
		storageName = cfg.Storages[0].Name
	} else {
		return "", fmt.Errorf("配置中未找到任何存储，请使用-storage-name参数指定")
	}

	// 连接到Gitaly服务
	address := cfg.SocketPath
	if address == "" {
		address = "unix:/tmp/gitaly.socket"
	}

	conn, err := client.DialContext(ctx, address, client.DefaultDialOpts)
	if err != nil {
		return "", fmt.Errorf("连接Gitaly服务失败 (%s): %v", address, err)
	}
	defer conn.Close()

	// 创建ref服务客户端
	refClient := gitalypb.NewRefServiceClient(conn)

	// 查找指定引用
	findRefRequest := &gitalypb.FindRefRequest{
		Repository: &gitalypb.Repository{
			StorageName:  storageName,
			RelativePath: cmd.repositoryPath,
		},
		RefName: []byte(refName),
	}

	findRefResponse, err := refClient.FindRef(ctx, findRefRequest)
	if err != nil {
		return "", fmt.Errorf("查找引用失败: %v", err)
	}

	if findRefResponse.GetRef() == nil {
		return "", fmt.Errorf("引用 %s 不存在", refName)
	}

	return findRefResponse.GetRef().GetTarget(), nil
}

// fetchAllKeepAroundRefs fetches all keep-around references
func (cmd *archiveSubcommand) fetchAllKeepAroundRefs(ctx context.Context, cfg config.Cfg) (map[string]string, error) {
	// 确定存储名称
	var storageName string
	if cmd.storageName != "" {
		storageName = cmd.storageName
	} else if len(cfg.Storages) > 0 {
		storageName = cfg.Storages[0].Name
	} else {
		return nil, fmt.Errorf("配置中未找到任何存储，请使用-storage-name参数指定")
	}

	// 连接到Gitaly服务
	address := cfg.SocketPath
	if address == "" {
		address = "unix:/tmp/gitaly.socket"
	}

	conn, err := client.DialContext(ctx, address, client.DefaultDialOpts)
	if err != nil {
		return nil, fmt.Errorf("连接Gitaly服务失败 (%s): %v", address, err)
	}
	defer conn.Close()

	// 创建ref服务客户端
	refClient := gitalypb.NewRefServiceClient(conn)

	keepAroundRefs := make(map[string]string)

	// 使用ForEachRef获取所有keep-around引用
	forEachRefRequest := &gitalypb.ForEachRefRequest{
		Repository: &gitalypb.Repository{
			StorageName:  storageName,
			RelativePath: cmd.repositoryPath,
		},
		Patterns: []string{"refs/keep-around/*"},
	}

	forEachRefStream, err := refClient.ForEachRef(ctx, forEachRefRequest)
	if err != nil {
		return nil, fmt.Errorf("调用ForEachRef失败: %v", err)
	}

	for {
		response, err := forEachRefStream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("接收ForEachRef响应失败: %v", err)
		}

		for _, ref := range response.GetReferences() {
			if strings.HasPrefix(string(ref.GetName()), "refs/keep-around/") {
				keepAroundRefs[string(ref.GetName())] = ref.GetTarget()
			}
		}
	}

	return keepAroundRefs, nil
}
